package window;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.*;
import java.nio.Buffer;

import block.*;
import util.Util;

public class MainWindow extends JFrame {

    int row, column;
    int unit = 80, margin = 20, info = 150;
    int width, height;
    boolean[][] taken;
    int[][] cond;
    LogicBlock[][] blocks;
    String[] addStrs = {"startX", "startY", "width", "height", "type"};
    String[] modStrs = {"startX", "startY"};
    String[] affStrs = {"startX", "startY", "width"};
    String[] confStrs = {"Input", "Output"};
    String addStr = "OR: -1 AND: 1 NOT: 0";
    JPanel buttons;
    int outputColumn;
    int blockNum = 0;


    public MainWindow() {
        super("Program");

        /*String strRow = JOptionPane.showInputDialog("Please enter input number");
        row = Integer.parseInt(strRow);
        String strColumn = JOptionPane.showInputDialog("Please enter column number");
        column = Integer.parseInt(strColumn);*/
        row = 6; column = 6;
        width = unit * column + margin * 2;
        height = unit * row + margin * 2 + info;
        outputColumn = -1;
        setVisible(true);
        setSize(width, height);
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        cond = new int[column + 1][row];
        for(int i = 0; i < column + 1; i ++)
            for(int j = 0; j < row; j ++)
                cond[i][j] = -1;
        blocks = new LogicBlock[column][row];
        taken = new boolean[column][row];
        for(int i = 0; i < column; i ++)
            for(int j = 0; j < row; j ++)
                taken[i][j] = false;

        buttons = new JPanel();
        JButton add = new JButton("Add");
        add.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPaneMultiInput mi = new JOptionPaneMultiInput(addStrs, addStr);
                int[] inputs = mi.show();
                if(inputs[2] <= 0 && inputs[3] <= 0){
                    JOptionPane.showMessageDialog(null, "Error: NonPositiveLength");
                    return;
                }
                if(inputs[0] < 0 || inputs[0] >= column || inputs[1] < 0 || inputs[1] >= row || inputs[0] + inputs[2] > column || inputs[1] + inputs[3] > row){
                    JOptionPane.showMessageDialog(null, "Error: CoordinateOutOfBound");
                    return;
                }
                if(isCrash(inputs[0], inputs[1], inputs[2], inputs[3])) {
                    JOptionPane.showMessageDialog(null, "Error: BlockCrash");
                    return;
                }
                if(inputs[4] != -1 && inputs[4] != 0 && inputs[4] != 1){
                    JOptionPane.showMessageDialog(null, "Error: WrongType");
                    return;
                }
                else if(inputs[4] == 0 && inputs[3] != 1){
                    JOptionPane.showMessageDialog(null, "Error: MultiInputNot");
                    return;
                }
                blocks[inputs[0]][inputs[1]] = new SimpleLogicBlock(inputs, taken);
                blockNum ++;
                repaint();
            }
        });
        buttons.add(add);

        JButton mod = new JButton("Mod");
        mod.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPaneMultiInput mi0 = new JOptionPaneMultiInput(modStrs, "(Modify coordinate)");
                int[] modify = mi0.show();
                if(modify[0] < 0 || modify[0] >= column || modify[1] < 0 || modify[1] >= row){
                    JOptionPane.showMessageDialog(null, "Error: CoordinateOutOfBound");
                    return;
                }
                if(blocks[modify[0]][modify[1]] == null){
                    JOptionPane.showMessageDialog(null, "Error: CoordinateError");
                    return;
                }
                blocks[modify[0]][modify[1]].invalidateBlock(cond, taken);
                JOptionPaneMultiInput mi1 = new JOptionPaneMultiInput(addStrs, addStr);
                int[] inputs = mi1.show();
                blocks[modify[0]][modify[1]].fulfill(inputs);
                if(inputs[2] <= 0 && inputs[3] <= 0){
                    JOptionPane.showMessageDialog(null, "Error: NonPositiveLength");
                    return;
                }
                if(inputs[0] < 0 || inputs[0] >= column || inputs[1] < 0 || inputs[1] >= row || inputs[0] + inputs[2] > column || inputs[1] + inputs[3] > row){
                    JOptionPane.showMessageDialog(null, "Error: CoordinateOutOfBound");
                    return;
                }
                if(isCrash(inputs[0], inputs[1], inputs[2], inputs[3])) {
                    JOptionPane.showMessageDialog(null, "Error: BlockCrash");
                    return;
                }
                if(inputs[4] != -1 && inputs[4] != 0 && inputs[4] != 1){
                    JOptionPane.showMessageDialog(null, "Error: WrongType");
                    return;
                }
                else if(inputs[4] == 0 && inputs[3] != 1){
                    JOptionPane.showMessageDialog(null, "Error: MultiInputNot");
                    return;
                }
                blocks[modify[0]][modify[1]] = new SimpleLogicBlock(inputs, taken);
                repaint();
            }
        });
        buttons.add(mod);

        JButton del = new JButton("Del");
        del.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPaneMultiInput mi = new JOptionPaneMultiInput(modStrs, "(Delete coordinate)");
                int[] delete = mi.show();

                if(delete[0] < 0 || delete[0] >= column || delete[1] < 0 || delete[1] >= row){
                    JOptionPane.showMessageDialog(null, "Error: CoordinateOutOfBound");
                    return;
                }
                if(blocks[delete[0]][delete[1]] == null){
                    JOptionPane.showMessageDialog(null, "Error: CoordinateError");
                    return;
                }
                blocks[delete[0]][delete[1]].invalidateBlock(cond, taken);
                blocks[delete[0]][delete[1]] = null;
                blockNum --;
                repaint();
            }
        });
        buttons.add(del);

        JButton set = new JButton("Set");
        set.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String inputStr = JOptionPane.showInputDialog("Set inputs:");
                int i;
                for(i = 0; i < inputStr.length(); i ++){
                    if(inputStr.charAt(i) == '1')
                        cond[0][i] = 1;
                    else if(inputStr.charAt(i) == '0')
                        cond[0][i] = 0;
                    else {
                        JOptionPane.showMessageDialog(null, "Error: WrongInputFormat");
                        for(int j = 0; j < i; j ++)
                            cond[0][j] = -1;
                        break;
                    }
                }
                for(int j = i; j < row; j ++)
                    cond[0][j] = -1;
                repaint();
            }
        });
        buttons.add(set);

        JButton save = new JButton("Save");
        save.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {

                int inputNum = getInputNum();
                if(inputNum == 0){
                    JOptionPane.showMessageDialog(null, "Error: NoInputPortFound");
                    return;
                }
                int outputNum = getOutputNum();
                if(outputNum == -1){
                    JOptionPane.showMessageDialog(null, "Error: NoOutputPortFound");
                    return;
                }
                String fileName = JOptionPane.showInputDialog("Please enter block name:");
                String fileName1 = fileName + ".dat";
                String fileName2 = fileName + ".format";
                try {
                    CombinationBlock.writeToFile(fileName1, inputNum, outputNum, calAllOutputs(inputNum, outputNum));
                    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(fileName2));
                    System.out.println("blockNum: " + blockNum);
                    for(int i = 0; i < column; i ++)
                        for (int j = 0; j < row; j++)
                            if (blocks[i][j] != null)
                                out.writeObject(blocks[i][j]);
                    out.flush();
                    out.close();
                }catch(Exception ex){
                    ex.printStackTrace();
                    JOptionPane.showMessageDialog(null, "Error: IOException");
                    return;
                }
            }
        });
        buttons.add(save);

        JButton aff = new JButton("AFF");
        aff.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String name = JOptionPane.showInputDialog("Please enter block name:");
                BufferedInputStream in;
                int inputNum, outputNum, blockRow;
                int[] allOutputs;

                try {
                    in = new BufferedInputStream(new FileInputStream(name + ".dat"));
                }catch(Exception ex){
                    ex.printStackTrace();
                    JOptionPane.showMessageDialog(null, "Error: IOException");
                    return;
                }
                try{
                    inputNum = in.read();
                    outputNum = in.read();
                    allOutputs = new int[(int)Math.pow(2, inputNum)];
                    for(int i = 0; i < allOutputs.length; i ++)
                        allOutputs[i] = in.read();
                }catch(Exception ex){
                    JOptionPane.showMessageDialog(null, "Error: IOException");
                    return;
                }

                blockRow = inputNum > outputNum ? inputNum : outputNum;
                JOptionPaneMultiInput mi = new JOptionPaneMultiInput(affStrs, "height:" + blockRow);
                int[] inputs = mi.show();

                if(inputs[2] <= 0){
                    JOptionPane.showMessageDialog(null, "Error: NonPositiveLength");
                    return;
                }
                if(inputs[0] < 0 || inputs[0] >= column || inputs[1] < 0 || inputs[1] >= row || inputs[0] + inputs[2] > column || inputs[1] + blockRow > row){
                    JOptionPane.showMessageDialog(null, "Error: CoordinateOutOfBound");
                    return;
                }
                if(isCrash(inputs[0], inputs[1], inputs[2], blockRow)) {
                    JOptionPane.showMessageDialog(null, "Error: BlockCrash");
                    return;
                }

                blocks[inputs[0]][inputs[1]] = new CombinationBlock(inputs[0], inputs[1], blockRow, inputs[2], inputNum, outputNum, allOutputs, taken, name);
                blockNum ++;
                repaint();
            }
        });
        buttons.add(aff);

        JButton conf = new JButton("Conf");
        conf.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPaneMultiInput mi1 = new JOptionPaneMultiInput(modStrs, "(Config coordinate)");
                int[] config = mi1.show();

                if(config[0] < 0 || config[0] >= column || config[1] < 0 || config[1] >= row){
                    JOptionPane.showMessageDialog(null, "Error: CoordinateOutOfBound");
                    return;
                }
                if(blocks[config[0]][config[1]] == null){
                    JOptionPane.showMessageDialog(null, "Error: CoordinateError");
                    return;
                }
                if(!(blocks[config[0]][config[1]] instanceof CombinationBlock)){
                    JOptionPane.showMessageDialog(null, "Error: WrongConfigObject");
                    return;
                }

                CombinationBlock cb = (CombinationBlock)blocks[config[0]][config[1]];
                JOptionPaneMultiInput mi2 = new JOptionPaneMultiInput(confStrs, "I:" + cb.getInputPorts() + "O:" + cb.getOutputPorts());
                int[] inputs = mi2.show();
                if(!cb.changePort(inputs[0], inputs[1])){
                    JOptionPane.showMessageDialog(null, "Error: InvalidInput");
                    return;
                }
                repaint();
            }
        });
        buttons.add(conf);

        JButton open = new JButton("open");
        open.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if(blockNum != 0){
                    JOptionPane.showMessageDialog(null, "Error: NotEmpty");
                    return;
                }
                String name = JOptionPane.showInputDialog("Please enter block name:");
                ObjectInputStream in;
                try {
                    in = new ObjectInputStream(new FileInputStream(name + ".format"));
                }catch(Exception ex){
                    JOptionPane.showMessageDialog(null, "Error: IOException");
                    return;
                }
                while(true){
                    try {
                        LogicBlock lb = (LogicBlock) in.readObject();
                        blocks[lb.startX][lb.startY] = lb;
                        lb.take(taken);
                        blockNum ++;
                    }catch(EOFException ex){
                        break;
                    }catch(IOException ex){
                        ex.printStackTrace();
                        JOptionPane.showMessageDialog(null, "Error: IOException");
                        return;
                    }catch(ClassNotFoundException ex){
                        JOptionPane.showMessageDialog(null, "Error: CNFException");
                        return;
                    }
                }
                repaint();
            }
        });
        buttons.add(open);

        JButton reset = new JButton("Res");
        reset.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                for(int i = 0; i < column; i ++) {
                    for (int j = 0; j < row; j++) {
                        taken[i][j] = false;
                        blocks[i][j] = null;
                    }
                }
                for(int i = 0; i < column + 1; i ++)
                    for(int j = 0; j < row; j ++)
                        cond[i][j] = -1;
                blockNum = 0;
                repaint();
            }
        });
        buttons.add(reset);

        add(buttons);
    }


    @Override
    public void paint(Graphics g){
        g.clearRect(0, 0, width, height);
        g.drawLine(margin, info + margin,margin, height - margin); // draw border
        g.drawLine(margin, height - margin, width - margin, height - margin);
        g.drawLine(width - margin, height - margin, width - margin, info + margin);
        g.drawLine(width - margin, info + margin, margin, info + margin);

        for(int i = 0; i < column; i ++) // draw numbers
            g.drawString(Integer.toString(i), margin + i * unit, info);
        for(int i = 0; i < row; i ++)
            g.drawString(Integer.toString(i), 0, info + margin + i * unit);

        for(int i = 0; i < row; i ++) // draw inputs
            g.drawString(cond[0][i] == -1 ? "" : Integer.toString(cond[0][i]), margin / 2, info + margin + unit / 2 + unit * i);

        for(int i = 0; i < column; i ++)
            for(int j = 0; j < row; j ++){ // draw blocks
            if (blocks[i][j] != null) {
                blocks[i][j].run(cond);
                blocks[i][j].draw(g, unit, margin, info + margin);
            }
        }

        /*for(int i = 1; i < column + 1; i ++)
            for(int j = 0; j < row; j ++)
                g.drawString(Integer.toString(cond[i][j]),margin - unit/2 + i * unit, info + margin + unit/2 + j * unit);*/
    }

    public boolean isCrash(int startX, int startY, int column, int row){
        for(int i = startX; i < startX + column; i ++) {
            for (int j = startY; j < startY + row; j ++)
                if (taken[i][j])
                    return true;
        }
        return false;
    }

    public int getInputNum(){
        int i;
        for(i = 0; i < row; i ++)
            if(cond[0][i] == -1)
                break;
        return i;
    }

    public int getOutputNum(){
        outputColumn = -1;
        for(int i = column - 1; i > -1; i --) {
            for (int j = 0; j < row; j++) {
                if (cond[i][j] != -1) {
                    outputColumn = i;
                    break;
                }
            }
            if (outputColumn != -1)
                break;
        }
        if(outputColumn == -1) return -1;
        int num = 0;
        for(int i = 0; i < row; i ++) {
            if (blocks[outputColumn - 1][i] != null && cond[outputColumn][i] != -1)
                if(blocks[outputColumn - 1][i] instanceof CombinationBlock)
                    num += ((CombinationBlock)blocks[outputColumn - 1][i]).outputNum;
                else
                    num++;
        }
        return num;
    }

    public int[] calAllOutputs(int inputNum, int outputNum){
        int length = (int)Math.pow(2, inputNum);
        int[] allOutputs = new int[length];
        int[] old = new int[row];
        int[] output = new int[outputNum];
        for(int i = 0; i < row; i ++)
            old[i] = cond[0][i];
        for(int i = 0; i < length; i ++){
            int[] inputs = Util.DecToBi(row, i);
            cond[0] = inputs;
            for(int m = 0; m < outputColumn; m ++){
                for(int n = 0; n < row; n ++){
                    if(blocks[m][n] != null)
                        blocks[m][n].run(cond);
                }
            }
            int j = 0;
            for(int k = 0; k < row; k ++){
                if(cond[outputColumn][k] != -1 && blocks[outputColumn - 1][k] != null) {
                    if(blocks[outputColumn - 1][k] instanceof CombinationBlock)
                        for (int m = 0; m < ((CombinationBlock) blocks[outputColumn - 1][i]).outputNum; m++)
                            output[j++] = cond[outputColumn][k + m];
                    else
                        output[j ++] = cond[outputColumn][k];
                }
            }
            allOutputs[i] = Util.BiToDec(output);
        }
        cond[0] = old;
        return allOutputs;
    }

    public static void main(String[] args){
        MainWindow mw = new MainWindow();
    }
}
